package com.yeskery.nut.core;

import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.bean.NoSuchBeanException;
import com.yeskery.nut.http.AbstractWrapperRequest;
import com.yeskery.nut.http.BaseRequest;
import com.yeskery.nut.http.controller.DefaultController;
import com.yeskery.nut.plugin.*;
import com.yeskery.nut.util.ExceptionUtils;
import com.yeskery.nut.view.ViewHandler;

import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;

/**
 * <p>{@link NutDispatcher}是Nut应用用来处理Web请求的核心处理对象，该类是{@link Dispatcher} 的默认实现类，无论Nut使用何种应用方式启动应用
 * (例如：Tomcat、Jetty、Netty等)，最终都会进入到该类进行处理。{@link #dispatch(Request, Response)}该方法是核心处理方法，通过该方法可以实现
 * Controller查找、WebPlugin的执行、Controller执行、异常处理等一系列Web操作。</p>
 *
 * <p>{@link NutDispatcher}是Nut用来管理{@link Controller}、{@link Plugin}的类，并还具有生成插件绑定上下文{@link BindContext}的作用，
 * 以及负责视图处理器{@link ViewHandler}的管理。</p>
 * @author sprout
 * 2019-03-16 11:25
 * @version 1.0
 *
 * @see com.yeskery.nut.core.Dispatcher
 */
public class NutDispatcher implements Dispatcher, Forward, Execution {

	/** 日志对象 */
	private static final Logger logger = Logger.getLogger(NutDispatcher.class.getName());

	/** Controller 管理器 */
	private final ControllerManager controllerManager;

	/** 插件管理器 */
	private final PluginManager pluginManager;

	/** 绑定上下文 */
	private final BindContext bindContext;

	/** 应用上下文 */
	private final ApplicationContext applicationContext;

	/** 视图处理器 */
	private final ViewHandler viewHandler;

	/**
	 * 构造一个请求转发器对象
	 * @param controllerManager Controller 管理器
	 * @param pluginManager 插件管理器
	 * @param bindContext 绑定上下文对象
	 * @param applicationContext 引用上下文
	 * @param viewHandler 视图处理器
	 */
	public NutDispatcher(ControllerManager controllerManager, PluginManager pluginManager, BindContext bindContext,
						 ApplicationContext applicationContext, ViewHandler viewHandler) {
		if (controllerManager == null) {
			throw new IllegalArgumentException("ControllerManager Must Not Be Null.");
		}
		if (pluginManager == null) {
			throw new IllegalArgumentException("PluginManager Must Not Be Null.");
		}
		this.controllerManager = controllerManager;
		this.pluginManager = pluginManager;
		this.viewHandler = viewHandler;
		this.bindContext = bindContext;
		this.applicationContext = applicationContext;
	}

	@Override
	public void dispatch(Request request, Response response) throws Exception {
		// 初始化请求上下文资源对象
		RequestApplicationContext.init();
		RequestApplicationContext.putResource(RequestApplicationContext.REQUEST_HOLDER_KEY, request);

		try {
			Controller controller = null;
			Collection<ControllerPlugin> controllerPlugins = pluginManager.getControllerPlugins();
			// controller插件前置处理
			for (ControllerPlugin controllerPlugin : controllerPlugins) {
				try {
					controller = controllerPlugin.beforeFindController(controllerManager, request);
					if (controller != null) {
						break;
					}
				} catch (Exception e) {
					String className = controllerPlugin.getClass().getName();
					logger.logp(Level.SEVERE, className, "beforeFindController",
							"Controller Plugin [" + className + "] Prefix Method Execute Failure.", e);
				}
			}

			// 查找controller
			if (controller == null) {
				controller = controllerManager.findController(request.getPath());
			}

			// controller插件后置处理
			for (ControllerPlugin controllerPlugin : controllerPlugins) {
				try {
					Controller afterController = controllerPlugin.afterFindController(controllerManager, request, controller);
					if (afterController != null) {
						controller = afterController;
						break;
					}
				} catch (Exception e) {
					String className = controllerPlugin.getClass().getName();
					logger.logp(Level.SEVERE, className, "afterFindController",
							"Controller Plugin [" + className + "] Postfix Method Execute Failure.", e);
				}
			}

			// 处理动态路径
			if (controller instanceof DynamicControllerAdapter) {
				DynamicControllerRegisterMetadata metadata = ((DynamicControllerAdapter) controller).getMetadata();
				if (metadata.getPathMatcher().match(request.getPath())) {
					Matcher matcher = metadata.getPathMatcher().getMatcher();
					for (int i = 0; i < metadata.getPathParameterNames().length; i++) {
						String key = metadata.getPathParameterNames()[i];
						String value = matcher.group(i + 1);
						if (key != null && value != null) {
							RequestApplicationContext.putResource(key, value);
						}
					}
				}
			}

			boolean pass = true;
			Collection<InterceptorPlugin> interceptorPlugins = pluginManager.getInterceptorPlugins();
			try {
				for (InterceptorPlugin interceptorPlugin : interceptorPlugins) {
					try {
						pass = interceptorPlugin.beforeHandle(request, response, this, controller);
						if (!pass) {
							break;
						}
					} catch (Exception e) {
						if (e instanceof ResponseNutException) {
							throw e;
						}
						String className = interceptorPlugin.getClass().getName();
						logger.logp(Level.SEVERE, className, "beforeHandle",
								"InterceptorPlugin Plugin [" + className + "] Prefix Method Execute Failure.", e);
					}
				}
				//执行对应的 controller 的请求对象
				if (pass) {
					validControllerRequest(request);
					controller.doRequest(request, response, this);
				}

				for (InterceptorPlugin interceptorPlugin : interceptorPlugins) {
					try {
						interceptorPlugin.afterHandle(request, response, controller, null);
					} catch (Exception e) {
						String className = interceptorPlugin.getClass().getName();
						logger.logp(Level.SEVERE, className, "afterHandle",
								"InterceptorPlugin Plugin [" + className + "] Postfix Method Execute Failure.", e);
					}
				}

				// 对未响应的请求进行空响应
				if (!response.isResponse()) {
					response.writeEmpty();
				}

			} catch (Exception e) {
				doExceptionResponse(controller, request, response, this, e);

				for (InterceptorPlugin interceptorPlugin : interceptorPlugins) {
					try {
						interceptorPlugin.afterHandle(request, response, controller, e);
					} catch (Exception e1) {
						String className = interceptorPlugin.getClass().getName();
						logger.logp(Level.SEVERE, className, "afterHandle",
								"InterceptorPlugin Plugin [" + className + "] Postfix Method Execute Failure.", e1);
					}
				}

			}
		} finally {
			// 清空请求上下文资源对象
			RequestApplicationContext.remove();
		}
	}

	@Override
	public void forward(Request request, Response response, Execution execution, String path, Method method) {
		Controller controller = controllerManager.findController(path);
		if (method == null) {
			controller.doRequest(request, response, execution);
			return;
		}
		switch (method) {
			case GET: controller.doGet(request, response, execution); break;
			case POST: controller.doPost(request, response, execution); break;
			case PUT: controller.doPut(request, response, execution); break;
			case DELETE: controller.doDelete(request, response, execution); break;
			case HEAD: controller.doHead(request, response, execution);break;
			case OPTIONS: controller.doOptions(request, response, execution);break;
			case TRACE: controller.doTrace(request, response, execution);break;
			default: controller.doRequest(request, response, execution);
		}
	}

	@Override
	public Forward getForward() {
		return this;
	}

	@Override
	public BindContext getBindContext() {
		return this.bindContext;
	}

	@Override
	public ViewHandler getViewHandler() {
		if (this.viewHandler == null) {
			throw new IllegalArgumentException("View Handler Not Configuration.");
		}
		return this.viewHandler;
	}

	/**
	 * 处理自定义异常响应
	 * @param request 请求
	 * @param response 响应
	 * @param execution 执行器
	 * @param e 异常
	 */
	private void doExceptionResponse(Controller controller, Request request, Response response, Execution execution, Exception e) {
		Exception exception = ExceptionUtils.getTargetException(e);
		Collection<ExceptionHandlePlugin> exceptionHandlePlugins = pluginManager.getExceptionHandlePlugins();
		for (ExceptionHandlePlugin exceptionHandlePlugin : exceptionHandlePlugins) {
			try {
				if (exceptionHandlePlugin.handleException(controller, request, response, execution, exception != null ? exception : e)) {
					return;
				}
			} catch (Exception e1) {
				String className = exceptionHandlePlugin.getClass().getName();
				logger.logp(Level.SEVERE, className, "handleException",
						"InterceptorPlugin Plugin [" + className + "] Method Execute Failure.", e1);
			}
		}

		ResponseNutException responseNutException = null;
		if (exception instanceof ResponseNutException) {
			responseNutException = (ResponseNutException) exception;
		}
		if (responseNutException == null) {
			responseNutException = new ResponseNutException(exception, ResponseCode.INTERNAL_SERVER_ERROR);
		}
		RequestApplicationContext.putResource(ErrorController.REQUEST_EXCEPTION_HOLDER_KEY, responseNutException);

		try {
			ErrorController errorController = applicationContext.getBean(ErrorController.class);
			forward(request, response, execution, errorController.getErrorPath(), errorController.getErrorMethod());
		} catch (NoSuchBeanException e1) {
			logger.log(Level.SEVERE, "An error occurred in the request.", exception);
			new DefaultController().doRequest(request, response, execution);
		}
	}

	/**
	 * 验证请求是否合法
	 * @param request 请求对象
	 */
	private void validControllerRequest(Request request) {
		Request checkRequest = request;
		if (request instanceof WebRequestHolder) {
			checkRequest = ((WebRequestHolder) request).getRequest();
		}
		if (checkRequest instanceof AbstractWrapperRequest) {
			if (((AbstractWrapperRequest) checkRequest).isOverflowRequestBodySize()) {
				throw new ResponseNutException("Request Body Size Overflow["
						+ request.getServerRequestConfiguration().getMaxRequestSize()
						+ "].", ResponseCode.REQUEST_ENTITY_TOO_LARGE);
			}
		} else if (checkRequest instanceof BaseRequest) {
			if (((BaseRequest) checkRequest).isOverflowRequestBodySize()) {
				throw new ResponseNutException("Request Body Size Overflow["
						+ request.getServerRequestConfiguration().getMaxRequestSize()
						+ "].", ResponseCode.REQUEST_ENTITY_TOO_LARGE);
			}
		}
	}
}
